package com.yys.excel.yysexcel.service.impl;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import cn.hutool.db.GlobalDbConfig;
import cn.hutool.json.JSONUtil;
import com.yys.excel.yysexcel.common.constant.ExcelStants;
import com.yys.excel.yysexcel.common.enums.ErrorStatus;
import com.yys.excel.yysexcel.common.exception.CustomException;
import com.yys.excel.yysexcel.common.exception.ServiceException;
import com.yys.excel.yysexcel.common.result.R;
import com.yys.excel.yysexcel.entity.ImportExportTask;
import com.yys.excel.yysexcel.entity.ImportExportTemplate;
import com.yys.excel.yysexcel.entity.entityBo.ImportExportParamBo;
import com.yys.excel.yysexcel.entity.JdbcConfig;
import com.yys.excel.yysexcel.entity.export.DictVo;
import com.yys.excel.yysexcel.entity.export.StyleVo;
import com.yys.excel.yysexcel.mapper.ImportExportMapper;
import com.yys.excel.yysexcel.service.ImportExportService;
import com.yys.excel.yysexcel.utils.*;
import com.yys.excel.yysexcel.utils.dbUtil.DbUtil;
import com.yys.excel.yysexcel.utils.dbUtil.JdbcDbUtil;
import com.yys.excel.yysexcel.utils.easyPoi.ExcelUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 注释
 *
 * @author lys
 * @date 2022/11/29
 */
@Service
@Slf4j
public class ImportExportServiceImpl implements ImportExportService {
    private static ExecutorService executorService = new ThreadPoolExecutor(
            3,
            10,
            30,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<Runnable>(30),
            // 拒绝： 抛出异常
            new ThreadPoolExecutor.AbortPolicy());


    /**
     * 项目运行前所要运行的方法
     */
    @PostConstruct
    public void projectRunBefore() {
        //数据库查询 关闭 字段全部转为小写
        GlobalDbConfig.setCaseInsensitive(false);
        //将状态为 等待执行和执行中的任务，加入到线程中
        List<ImportExportTask> tasks = ImportExportMapper.getNotExecute();
        for (ImportExportTask task : tasks) {
            restartTask(task);
        }
    }

    /**
     * 重新开始任务
     *
     * @param task task
     */
    private void restartTask(ImportExportTask task) {
        String params = task.getParameters();
        String id = task.getId();
        String opType = task.getOp_type();
        Long templateId = task.getTemplate_id();
        final ImportExportParamBo importExportParamBo = JSONUtil.toBean(params, ImportExportParamBo.class);
        importExportParamBo.setTemplateId(templateId+"").setSql(task.getSqls());
        if ("export".equals(opType)) {
            //导出任务
            Runnable runnable = () -> executExport(importExportParamBo, id);
            executorService.execute(runnable);
        }
        //如果关闭系统前导入了一些数据，这里会从头开始导入，就会出现重复导入的情况
        else if("import".equals(opType)){
            //将oss存储的导入文件，设置到对象中
            String op_file_name = task.getOp_file_name();
            InputStream fileStream = OssUtils.getFileStream(op_file_name);
            importExportParamBo.setInputStream(fileStream);
            //异步执行
            Runnable runnable = () -> importTaskExcel07SaxReader(importExportParamBo,id,op_file_name);
            executorService.execute(runnable);
        }
    }

    @Override
    public R exportByTempleIdDownload(ImportExportParamBo importExportParamBo, HttpServletResponse response) throws Exception {
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);

        String templateId = importExportParamBo.getTemplateId();
        ImportExportTemplate template = ImportExportMapper.getTemplateById(templateId);
        //获取数据库连接配置，如果为空出错，就用本地的链接
        String jdbcConfigId = template.getJdbc_config_id();
        JdbcConfig jdbcConfig = ImportExportMapper.getJdbcConfigById(jdbcConfigId);
        Db db = JdbcDbUtil.getDatabase(jdbcConfig);
        //获取sql数据
        List<Entity> datas = null;
        try {
            datas = db.query(importExportParamBo.getSql());
        }catch (Exception e){
            return R.fail("获取sql数据出错：" + e.getMessage());
        }

        //导出文件路径
        String filePath = FileUtils.getFilePathByName(RandomStrg.getGUID32());

        //如果模板是简单的sql导出
        if (template.getTemplate_type() == 1) {
            DictVo dictData = JSONUtil.toBean(template.getDict(), DictVo.class);
            StyleVo styleData = JSONUtil.toBean(template.getStyle(), StyleVo.class);
            if (dictData != null) {
                //列转换
                datas = dictConvert(dictData.getColumn(), datas);
                //标题转换 列行高样式设置
                ExcelUtils.exportExcel(datas, filePath, dictData.getTitle(), styleData);
            } else {
                //生成excel
                ExcelUtils.exportExcel(datas, filePath, styleData);
            }
        } else if (template.getTemplate_type() == 2) {
            //是excel模板的方式导出
            Integer fileType = template.getFile_type();
            String pathFile = template.getFile_path();
            if (fileType == 1) {
                //本地文件路径
                TemplateUtils.generateFileByTemplateFliePath(pathFile, datas, filePath);
            } else if (fileType == 2) {
                //oss文件路径
                TemplateUtils.generateFileByTemplateOssPath(pathFile, datas, filePath);
            }
        }

        //下载文件
        FileUtils.setAttachmentResponseHeader(response, filePath);
        FileUtils.writeBytes(filePath, response.getOutputStream());
        //删除本地文件
        FileUtil.del(filePath);
        return R.ok();
    }

    @Override
    public String exportByTempId(ImportExportParamBo importExportParamBo) {
        String id = ImportExportMapper.insertExportTask(importExportParamBo);
        //异步执行
        Runnable runnable = () -> executExport(importExportParamBo, id);
        //先来的先执行
        executorService.execute(runnable);
        return id;
    }

    /**
     * downloda文件通过id
     *
     * @param id       id
     * @param response 响应
     * @param fileName 文件名称
     * @return {@link R}
     */
    @Override
    public R downlodaFileById(String id, String fileName, HttpServletResponse response) {
        ImportExportTask task = ImportExportMapper.getImportExportTaskById(id);
        if (null == task) {
            return R.fail("任务不存在");
        }
        Integer status = task.getStatus();
        if (ErrorStatus.EXECUTOVER.getCode() != status) {
            return R.fail("任务未完成或异常");
        }
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        String op_file_name = task.getOp_file_name();
        //下载文件
        String filePath = OssUtils.downloadFileNow(op_file_name);
        if (StrUtil.isBlank(fileName)) {
            fileName = FileUtil.getName(op_file_name);
        } else {
            String fileType = FileUtils.getFileType(FileUtil.getName(op_file_name));
            fileName = FileUtils.getFileNameRomveSuffix(fileName) + "." + fileType;
        }
        try {
            //文件编码
            FileUtils.setAttachmentResponseHeader(response, fileName);
            FileUtils.writeBytes(filePath, response.getOutputStream());
            //删除本地文件
            FileUtil.del(filePath);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return R.ok("下载成功");
    }

    /**
     * 导出方法
     *
     * @param importExportParamBo 进出口参数波
     * @param id                  id
     */
    private void executExport(ImportExportParamBo importExportParamBo, String id) {
        //记录操作时间
        TimeInterval timer = DateUtil.timer();
        try {
            //状态改为正在执行 1
            ImportExportMapper.updateTaskStatus(id,ErrorStatus.EXECUTING.getCode(),importExportParamBo);
            //选择具体模板，进行相应的操作
            String templateId = importExportParamBo.getTemplateId();
            ImportExportTemplate template = ImportExportMapper.getTemplateById(templateId);
            if (ObjectUtil.isEmpty(template)) {
                opError(id, "未获取到模板数据", ErrorStatus.EXCEPTION.getCode(), timer);
            } else {
                //将模板更新到数据库中
                ImportExportMapper.updateTaskTemplate(id,templateId);
            }

            //获取数据库连接配置，如果为空出错，就用本地的链接
            String jdbcConfigId = template.getJdbc_config_id();
            JdbcConfig jdbcConfig = ImportExportMapper.getJdbcConfigById(jdbcConfigId);
            Db db = JdbcDbUtil.getDatabase(jdbcConfig);
            //获取sql数据
            List<Entity> datas = null;
            try {
                datas = db.query(importExportParamBo.getSql());
            }catch (SQLException e){
                opError(id, "获取sql数据出错：" + e.getMessage(), ErrorStatus.EXCEPTION.getCode(), timer);
            }
            if (IterUtil.isEmpty(datas)) {
                opError(id, "获取sql数据为空", ErrorStatus.EXCEPTION.getCode(), timer);
            }
            //导出文件路径
            String filePath = FileUtils.getFilePathByName(RandomStrg.getGUID32());

            //如果模板是简单的sql导出
            if (template.getTemplate_type() == 1) {
                DictVo dictData = YamlUtil.strToDictVo(template.getDict());
                StyleVo styleData = YamlUtil.strToStyleVo(template.getStyle());
                if (dictData != null) {
                    //列转换
                    datas = dictConvert(dictData.getColumn(), datas);
                    //标题转换 列宽行高样式设置
                    ExcelUtils.exportExcelThrow(id, datas, filePath, dictData.getTitle(), styleData, timer);
                } else {
                    //生成excel
                    ExcelUtils.exportExcel(id, datas, filePath, styleData, timer);
                }
            } else if (template.getTemplate_type() == 2) {
                //是excel模板的方式导出
                Integer fileType = template.getFile_type();
                String pathFile = template.getFile_path();
                if (StrUtil.isBlank(pathFile)) {
                    opError(id, "模板路径为空", ErrorStatus.EXCEPTION.getCode(), timer);
                }
                if (fileType == 1) {
                    //本地文件路径
                    TemplateUtils.generateFileByTemplateFliePath(pathFile, datas, filePath);
                } else if (fileType == 2) {
                    //oss文件路径
                    TemplateUtils.generateFileByTemplateOssPath(pathFile, datas, filePath);
                }
            }

            //将本地文件上传到oss中
            OssUtils.fileUpload(filePath, filePath,true);
            //记录数据库
            ImportExportMapper.updateTaskEnd(id,timer.intervalSecond(),"导出成功",filePath);
        } catch (CustomException e) {
            System.out.println("已经处理过异常");
        } catch (ServiceException e) {
            opError(id, e.getMessage(), e.getCode(), timer);
        } catch (Exception e) {
            opError(id, "未知错误：" + e.getMessage(), ErrorStatus.EXCEPTION.getCode(), timer);
        }
    }

    /**
     * 列字典转换
     *
     * @param columnDict 样板
     */
    private List<Entity> dictConvert(Map<String, Map<String, String>> columnDict, List<Entity> datas) {
        if (MapUtil.isEmpty(columnDict)) {
            return datas;
        }
        try {
            if (MapUtil.isNotEmpty(columnDict)) {
                datas = datas.stream().peek(e -> {
                    for (String k : e.keySet()) {
                        if (columnDict.keySet().contains(k)) {
                            //原来的value值
                            String originalV = e.getStr(k);
                            //转换后的value值
                            String v = Convert.toStr(columnDict.get(k).get(originalV), originalV);
                            e.set(k, v);
                        }
                    }
                }).collect(Collectors.toList());
            }
            return datas;
        } catch (Exception e) {
            throw new ServiceException(ErrorStatus.EXCEPTION.getCode(), "列字典转换出错：" + e.getMessage());
        }
    }

    /**
     * 封装异常时插入异常消息(异常结束)
     *
     * @param id     数据id
     * @param msg    异常消息
     * @param status 异常类型   -1异常 -2超时 -3主动终止 0等待执行 1执行中 2执行完成
     * @param timer  计时器
     */
    public void opError(String id, String msg, int status, TimeInterval timer) {
        log.error(msg);
        ImportExportMapper.opError(id,msg,status,timer);
        throw new CustomException(msg);
    }


    /**
     * 将文件内容导入到数据库中
     *
     * @param importData 导入数据
     * @return {@link String}
     */
    @Override
    public String importByFile(ImportExportParamBo importData) {
        String fileName = importData.getFile().getOriginalFilename();
        String fileType = FileUtils.getFileType(fileName);
        importData.setFileName(fileName).setFileType(fileType);
        String id = ImportExportMapper.insertTaskImport(importData);
        if (ObjectUtil.isEmpty(importData.getFile())) {
            opError(id, "导入文件为空", ErrorStatus.EXCEPTION.getCode(), DateUtil.timer());
        }
        String ossPath = ExcelStants.EXCEL_PATH + fileName;
        try {
            InputStream is = importData.getFile().getInputStream();
            //文件保存在本地，上传oss后会关闭流，导致后面拿不到文件流
            FileWriter writer = new FileWriter(ossPath);
            writer.writeFromStream(is,false);
            //将文件存储在oss中 ，以免项目重启时，可以再次执行该任务
            OssUtils.fileUpload(ossPath,ossPath,false);
            ImportExportMapper.updateTaskOpFileName(id,ossPath);
            //将文件流设置到对象中，防止异步时文件找不到
            importData.setInputStream(FileUtil.getInputStream(ossPath));
        } catch (IOException e) {
            opError(id, "文件转化为InputStream失败", ErrorStatus.EXCEPTION.getCode(), DateUtil.timer());
        }
        Runnable runnable = () -> importTaskExcel07SaxReader(importData, id,ossPath);
        executorService.execute(runnable);
        return id;
    }

    /**
     * 重启任务id
     *
     * @param id id
     */
    @Override
    public void restartTaskById(String id) {
        ImportExportTask task = ImportExportMapper.getImportExportTaskById(id);
        restartTask(task);
    }

    @Override
    public R downlodaTemplateOssFileById(Long id, String fileName, HttpServletResponse response) {
        ImportExportTemplate template = ImportExportMapper.getTemplateById(id+"");
        if(ObjectUtil.isEmpty(template)){
            return R.fail("模板不存在");
        }
        if(template.getTemplate_type() != 2){
            return R.fail("不是excel模板");
        }
        if(template.getFile_type() != 2){
            return R.fail("不是oss文件");
        }
        String ossPath = template.getFile_path();
        if(StrUtil.isBlank(ossPath)){
            return R.fail("oss路径为空");
        }
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        //下载文件
        String filePath = OssUtils.downloadFileNow(ossPath);
        if (StrUtil.isBlank(fileName)) {
            fileName = FileUtil.getName(ossPath);
        } else {
            String fileType = FileUtils.getFileType(FileUtil.getName(ossPath));
            fileName = FileUtils.getFileNameRomveSuffix(fileName) + "." + fileType;
        }
        try {
            //文件编码
            FileUtils.setAttachmentResponseHeader(response, fileName);
            FileUtils.writeBytes(filePath, response.getOutputStream());
            //删除本地文件
            FileUtil.del(filePath);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return R.ok("下载成功");
    }

    /**
     * 导入任务 大量数据，读取每一条数据，按1000条进行导入
     *
     * @param importData 导入数据
     * @param id         id
     */
    public void importTaskExcel07SaxReader(ImportExportParamBo importData, String id,String ossPath) {
        //记录操作时间
        TimeInterval timer = DateUtil.timer();
        //状态改为正在执行 1
        ImportExportMapper.updateTaskStatus(id,ErrorStatus.EXECUTING.getCode(),importData);
        try {
            //将title和column设置到对象中
            ReadUtils.setTitleColumn(importData, importData.getTemplateId());
            //执行导入方法
            ReadUtils.importTaskExcel07SaxReader(importData, id, timer);
            //插入成功后记录入库
            ImportExportMapper.updateTaskEnd(id,timer.intervalSecond(),"导入成功",null);
            //删除保存在本地的文件
            FileUtil.del(ossPath);
        } catch (CustomException e) {
            System.out.println("已经处理过异常");
        } catch (ServiceException e) {
            opError(id, e.getMessage(), e.getCode(), timer);
        } catch (Exception e) {
            opError(id, "数据入库失败：" + e.getMessage(), ErrorStatus.EXCEPTION.getCode(), timer);
        }
    }

    /**
     * 导入任务-直接读取excel数据
     */
    private void importTask(ImportExportParamBo importData, String id) {
        //记录操作时间
        TimeInterval timer = DateUtil.timer();
        //状态改为正在执行 1
        DbUtil.update(Entity.create(ExcelStants.IMPORT_EXPORT_TASK_TABLE).set("status", ErrorStatus.EXECUTING.getCode()), Entity.create().set("id", id));
        String fileType = importData.getFileType();
        List<Map<String, Object>> datas = null;
        try {
            datas = ReadUtils.readFile(importData.getInputStream(), fileType);
        } catch (Exception e) {
            opError(id, "读取文件数据出错：" + e.getMessage(), ErrorStatus.EXCEPTION.getCode(), timer);
        }
        if (IterUtil.isEmpty(datas)) {
            opError(id, "导入的数据为空", ErrorStatus.EXCEPTION.getCode(), timer);
        }
        //将 titleMap 和 columnMap 转换进去
        List<Entity> addDatas = null;
        try {
            //转换标题列
            addDatas = ReadUtils.converTitleColumn(datas, importData);
        } catch (Exception e) {
            opError(id, "转换标题或列出错：" + e.getMessage(), ErrorStatus.EXCEPTION.getCode(), timer);
        }
        try {
            DbUtil.insert(addDatas);
            //插入成功后记录入库
            Entity e = Entity.create(ExcelStants.IMPORT_EXPORT_TASK_TABLE).set("update_time", DateUtil.now())
                    .set("execute_time", timer.intervalSecond()).set("status", ErrorStatus.EXECUTOVER.getCode());
            //记录数据库
            DbUtil.update(e, Entity.create().set("id", id));
        } catch (Exception e) {
            opError(id, "数据入库失败：" + e.getMessage(), ErrorStatus.EXCEPTION.getCode(), timer);
        }
    }
}
